package org.manon.json.analyse;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

import org.manon.json.JSONException;
import org.manon.json.annotation.JSonDiscriminator;
import org.manon.json.annotation.JSonDiscriminatorType;
import org.manon.json.annotation.JSonProperty;
import org.manon.json.annotation.JSonPropertyIgnore;
import org.manon.json.meta.MetaProperty;
import org.manon.json.meta.MetaPropertyDiscriminator;
import org.manon.json.meta.MetaPropertyFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author <a href="mailto:jacques.militello@gmail.com">Jacques Militello</a>
 */
final class ClassAnalyser {

	public static final String PREFIX_GETTER = "get";
	
	public static final String PREFIX_SETTER = "set";
	
	/**
	 * Logger
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(ClassAnalyser.class);
	
	static Set<MetaProperty> analyse(Class<?> toRegister) {
		
		final Method[] methods = toRegister.getDeclaredMethods();
		sortMethod(methods);
		
		final Set<MetaProperty> properties = new LinkedHashSet<MetaProperty>();

		for (Method method : methods) {

			if (LOGGER.isTraceEnabled()) {
				LOGGER.trace("Analyse method [{}]", method);
			}
			
//			if (method.getName().length() < 4) {
//				if (LOGGER.isTraceEnabled()) {
//					LOGGER.trace("Method name too short [{}] -> skip", method);
//				}
//				continue;
//			}
			
			if (method.getName().startsWith(PREFIX_GETTER)) {
				
				if (method.getAnnotation(JSonPropertyIgnore.class) != null) {
					if (LOGGER.isDebugEnabled()) {
						LOGGER.debug("Ignore the property [{}] for Pojo [{}]", firstLetterToLowerCase(method.getName().substring(3)),toRegister.getName());
					}
					continue;
				}
				
				MetaProperty property = getProperty(toRegister,method);
				if (property != null) {
					properties.add(property);
				}
				continue;
			}
		}
		
		JSonDiscriminator discriminator = toRegister.getAnnotation(JSonDiscriminator.class);
		if (discriminator != null) {
			MetaPropertyDiscriminator property = new MetaPropertyDiscriminator(discriminator.name(),discriminator.name(), String.class);
			property.setType(discriminator.type());
			property.setProperty(discriminator.property());
			properties.add(property);
			
			if (discriminator.type() == JSonDiscriminatorType.PROPERTY) {
				// remove property with the discriminator.property()
				Iterator<MetaProperty> iterator = properties.iterator();
				while (iterator.hasNext()) {
					MetaProperty metaProperty = iterator.next();
					if (metaProperty.getName().equals(discriminator.property())) {
						iterator.remove();
						break;
					}
				}
			}
			
		}
		return properties;
	}
	
	
	/**
	 * 	Sort Array to have "get" before "set"
	 */
	private static void sortMethod(Method[] methods) {
		Arrays.sort(methods, new Comparator<Method>() {
			public int compare(Method m1, Method m2) {
				return m1.getName().compareTo(m2.getName());
			}
		});
	}

	private static MetaProperty getProperty(Class<?> clazz,Method method) {
		final String rawProperty = method.getName().substring(3);
		
		// check if setter exists ?
		try {
			clazz.getMethod(PREFIX_SETTER + rawProperty, method.getReturnType());
		} catch (SecurityException e) {
			throw new JSONException(e.getMessage(),e);
		} catch (NoSuchMethodException e) {
			LOGGER.warn("No Setter for Getter [{}] -> SKIP",method.toString());
			return null;
		}
		
		final String property = firstLetterToLowerCase(rawProperty);
		final JSonProperty jsonProperty = method.getAnnotation(JSonProperty.class);
		return MetaPropertyFactory.newInstance(jsonProperty,property,method);
	}
	
	private static String firstLetterToLowerCase(String string) {
		final StringBuilder builder = new StringBuilder();
		builder.append(Character.toLowerCase(string.charAt(0)));
		if (string.length() > 1) {
			builder.append(string.substring(1));
		}
		return builder.toString();
	}
}